package com.xiyuan.smartutils.crypto;

import com.xiyuan.smartutils.Asserts;
import com.xiyuan.smartutils.Bytes;
import com.xiyuan.smartutils.Validates;
import com.xiyuan.smartutils.constants.CodeConstants;

import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
 * Base64编码<br>
 * <p>
 * 把3个8位字节（3*8=24）转化为4个6位的字节（4*6=24），之后在每个6位的前面补两个0，形成8位，即一个字节。<br>
 * 再根据每个字节的值，用下表中的值替换，不足4个字节的，后补'='。<br><br>
 * <p>
 * 从BASE64表[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/]中取值。
 * <p>
 * 例如，GBK（0xB7B2BAFC），先将三个字节（0xB7B2BA）分割成四个字节（0x2D3B0A3A），用BASE64表值替换为（t7K6）；<br>
 * 再将（0xBD）分割成（0x3F00）用BASE64表值替换为（/A），再补两个（=），合起来就形成了编码后的字符串（t7K6/A==）。<br>
 * <p>
 * 1、编码时返回的字符串无需指定字符集，但编码前的字符串需要<br>
 * 2、解码时原字符串无需指定字符集，但解码后的字符串需要<br>
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */

@SuppressWarnings("ALL")
public class Base64 implements CodeConstants {
    
    /**********************************************************/
    /****** BASE64标准64个字符，和支持的填充字符（等号和回车换行）******/
    /**********************************************************/
    public static final String _BASE64_STD_ = _LETTERS_UPPERCASE_ + _LETTERS_LOWERCASE_ + _DIGITS_ + "+/";
    public static final String _BASE64_EXT_ = _BASE64_STD_ + "=\r\n";
    
    // 64个BASE64字符
    public static final byte[] _BASE64_BS_ = _BASE64_STD_.getBytes();
    
    // 不足8的倍数填充符
    public static final byte _BASE64_PAD_ = (byte) '=';
    
    // 256个byte位置，用于反向取letter位置值
    private static final byte[] BASE64_BYTE = new byte[256];
    
    // 初始化一个BASE64有效字节数组，类似于建一个哈希表，通过letter获取byte
    static {
        
        // 先对所有的byte赋值为-1
        Arrays.fill(BASE64_BYTE, (byte) -1);
        
        // 对letter赋值为letter的位置值，如A=0,a=26,0=52,+=62,/=63
        for (int i = 0; i < _BASE64_BS_.length; i++) {
            byte b = _BASE64_BS_[i];
            BASE64_BYTE[b] = (byte) i;
        }
    }
    
    
    /** Base64解码 */
    /**
     * 删除不在BASE64的其他字符，如可能的\r\n
     */
    private static byte[] removeNonBase64(byte[] src) {
        int validLen = 0;
        byte[] valid = new byte[src.length];
        for (byte b : src) {
            if (b < '+' || b > 'z') {
                continue;//'+'最小=43,'z'最大=122
            }
            
            byte v = BASE64_BYTE[b];
            if (v == -1) {continue;}
            
            valid[validLen++] = b;
        }
        
        return Bytes.getBytes(valid, 0, validLen);
    }
    
    public static byte[] encrypt(Byte[] src){
        return doEncrypt(src);
    }
    
    private static byte[] doEncrypt(Object src){
        // 第一步，计算编码后的长度
        int len = Array.getLength(src);
        int length = len / 3;
        int remainderLen = len % 3;
        
        // 3字节换成4字节后的长度，余数补成1个字节
        int destLen = (remainderLen == 0) ? length * 4 : (length + 1) * 4;
        byte[] dest = new byte[destLen];
        
        // 第二步，对标准长度进行处理
        for (int i = 0; i < length; i++) {
            int srcIndex = i * 3;
            int destIndex = i * 4;
            
            byte s1 = (byte)Array.get(src,srcIndex + 0);
            byte s2 = (byte)Array.get(src,srcIndex + 1);
            byte s3 = (byte)Array.get(src,srcIndex + 2);
            
            // 将3字节按网络顺序转成4字节(byte位运算时先通过&0xFF转成无符号整型)，格式为[00000000 S1S1S1S1 S2S2S2S2 S3S3S3S3]
            int d = 0 << 24 | (s1 & 0xFF) << 16 | (s2 & 0xFF) << 8 | (s3 & 0xFF);
            
            // 分别取8-13,14-19,20-25,26-31，与0x3F作运算前补0
            dest[destIndex + 0] = _BASE64_BS_[d >>> 18 & 0x3F];// 取00000000 00000000 00000000 00S1S1S1 & 00000000 00000000 00000000 00111111 = 00S1S1S1
            dest[destIndex + 1] = _BASE64_BS_[d >>> 12 & 0x3F];// 取00000000 00000000 0000S1S1 S1S1S2S2 & 00000000 00000000 00000000 00111111 = 00S1S2S2
            dest[destIndex + 2] = _BASE64_BS_[d >>>  6 & 0x3F];// 取00000000 000000S1 S1S1S1S2 S2S2S2S3 & 00000000 00000000 00000000 00111111 = 00S2S2S3
            dest[destIndex + 3] = _BASE64_BS_[d >>>  0 & 0x3F];// 取00000000 S1S1S1S1 S2S2S2S2 S3S3S3S3 & 00000000 00000000 00000000 00111111 = 00S3S3S3
        }
        
        if (remainderLen == 0) {return dest;}
        
        // 第三步，对剩余的长度进行处理(remainderLen=[1,2])
        int srcIndex = length * 3;
        int destIndex = length * 4;
        if (remainderLen == 1) {
            // 剩1个字节
            byte s1 = (byte)Array.get(src, srcIndex);
            
            // 将1个字节转成格式为[00000000 00000000 0000S1S1 S1S10000]，右补4个0保证每6位一组
            int d = (s1 & 0xFF) << 4;
            
            // 取00000000 00000000 00000000 00S1S1S1 & 00000000 00000000 00000000 00111111 = 00S1S1S1
            dest[destIndex + 0] = _BASE64_BS_[d >>> 6 & 0x3F];
            
            // 取00000000 00000000 00S1S1S1 S1S10000 & 00000000 00000000 00000000 00111111 = 00S10000
            dest[destIndex + 1] = _BASE64_BS_[d & 0x3F];
            
            // 补码
            dest[destIndex + 2] = _BASE64_PAD_;
            dest[destIndex + 3] = _BASE64_PAD_;
        }
        else {// 剩2个字节
            byte s1 = (byte)Array.get(src, srcIndex);
            byte s2 = (byte)Array.get(src, srcIndex + 1);
            
            // 将2个字节转成格式为[00000000 000000S1 S1S1S1S2 S2S2S200]，右补2个0保证每6位一组，原2*8=16变为18=3*6
            int d = (s1 & 0xFF) << 10 | (s2 & 0xFF) << 2;
            
            // 第一字节取原1字节00123456，第二字节取原1字节0078+2字节1234，第三字节取2字节00567800
            
            // 取00000000 00000000 000000S1 00S1S1S1 & 00000000 00000000 00000000 00111111 = 00S1S1S1
            dest[destIndex + 0] = _BASE64_BS_[d >>> 12 & 0x3F];
            
            // 取00000000 00000000 0000S1S1 S1S1S2S2 & 00000000 00000000 00000000 00111111 = 00S1S2S2
            dest[destIndex + 1] = _BASE64_BS_[d >>> 6 & 0x3F];
            
            // 取00000000 000000S1 S1S1S1S2 S2S2S200 & 00000000 00000000 00000000 00111111 = 00S2S200
            dest[destIndex + 2] = _BASE64_BS_[d & 0x3F];
            
            // 补码
            dest[destIndex + 3] = _BASE64_PAD_;
        }
        
        return dest;
        
    }
    /**
     * Base64编码
     */
    public static byte[] encrypt(byte[] src) {
        return doEncrypt(src);
        
    }
    
    
    /**
     * Base64解码
     */
    public static byte[] decrypt(byte[] src) {
        // 第一步，去除不是BASE64表里的字符，包括'='号
        src = removeNonBase64(src);
        if (src.length == 0) {return new byte[0];}
        
        // 第二步，取得letter对应的byte原值
        for (int i = 0; i < src.length; i++) {
            src[i] = BASE64_BYTE[src[i]];
        }
        
        // 第三步，每4个字节转换成3个字节
        int length = src.length / 4;
        int remainderLen = src.length % 4;
        
        // [0,1,2]
        int destLen = length * 3 + ((remainderLen == 0) ? 0 : ((remainderLen == 2) ? 1 : 2));
        byte[] dest = new byte[destLen];
        
        for (int i = 0; i < length; i++) {
            int srcIndex = i * 4;
            int destIndex = i * 3;
            
            byte s1 = src[srcIndex];
            byte s2 = src[srcIndex + 1];
            byte s3 = src[srcIndex + 2];
            byte s4 = src[srcIndex + 3];
            
            // 将4个字节按网络顺序转成无符号整型，格式为[00000000 S1S1S1 S2S2S2 S3S3S3 S4S4S4]
            int d = (s1 & 0xFF) << 18 | (s2 & 0xFF) << 12 | (s3 & 0xFF) << 6 | (s4 & 0xFF);
            
            dest[destIndex] = (byte) (d >> 16);
            dest[destIndex + 1] = (byte) (d >> 8);
            dest[destIndex + 2] = (byte) (d);
        }
        
        if (remainderLen == 0) {return dest;}
        
        // 第四步，最剩余字节处理(remainderLen=[2,3])
        int srcIndex = length * 4;
        int destIndex = length * 3;
        if (remainderLen == 2) {
            // 还剩2个字节，对应解码为1个字节
            byte s1 = src[srcIndex];
            byte s2 = src[srcIndex + 1];
            
            // 将2个字节按网络顺序转成无符号整型，格式为[00000000 000000 000000 S1S1S1 S20000]
            int s = (s1 & 0xFF) << 6 | (s2 & 0xFF);
            dest[destIndex] = (byte) (s >> 4);
        }
        else {// 还剩3个字节，对应解码为2个字节
            byte s1 = src[srcIndex + 0];
            byte s2 = src[srcIndex + 1];
            byte s3 = src[srcIndex + 2];
            
            // 将3个字节按网络顺序转成无符号整型，格式为[00000000 000000 000000 S1S1S1 S2S2S2 S3S300]
            // 00000000 000000 0000S1 S1S1S1 000000 000000
            // 00000000 000000 000000 0000S2 S2S2S2 000000
            // 00000000 000000 000000 0000S2 0000S3 S3S3S3
            // 00000000 000000 0000S1 S1S1S1 S2S2S2 S3S3S3
            int s = (s1 & 0xFF) << 12 | (s2 & 0xFF) << 6 | (s3 & 0xFF);
            dest[destIndex + 0] = (byte) (s >> 10);
            dest[destIndex + 1] = (byte) (s >> 2);
        }
        
        return dest;
    }
    
    /*************************************************************************************************/
    // 标准编解码
    /*************************************************************************************************/
    
    /**
     * 把字节数组编码成字符串
     */
    public static String encode(byte[] src) {
        Asserts.nonNull(src);
        return new String(encrypt(src), _UTF_8_C_);
    }
    
    /**
     * 把字节数组编码成字符串
     */
    public static String encode(Byte[] src) {
        Asserts.nonNull(src);
        return new String(encrypt(src), _UTF_8_C_);
    }
    
    
    /**
     * 把字符串解码成字节数组
     */
    public static byte[] decode(String src) {
        Asserts.nonNull(src, _SRC_);
        Asserts.as(Validates.isScope(src, _BASE64_EXT_) ? null : "参数含有不是Base64字符串允许的字符");
        
        return decrypt(src.getBytes(_UTF_8_C_));
    }
    
    /*************************************************************************************************/
    // 指定UTF-8编码的字符串编解码
    /*************************************************************************************************/
    
    /**
     * 使用UTF-8进行Base64编码
     */
    public static String encodeUTF8(String src) {
        return encode(src, _UTF_8_C_);
    }
    
    /**
     * 使用UTF-8进行Base64解码
     */
    public static String decodeUTF8(String src) {
        return decode(src, _UTF_8_C_);
    }
    
    /*************************************************************************************************/
    // 指定${字符集}的字符串编解码
    /*************************************************************************************************/
    
    /**
     * 使用指定编码格式进行Base64编码
     */
    public static String encode(String src, String encoding) {
        return encode(src, Charset.forName(encoding));
    }
    
    /**
     * 使用指定编编码进行Base64编码
     */
    public static String encode(String src, Charset charset) {
        return encode(src.getBytes(charset));
    }
    
    /**
     * 指定编码格式进行Base64解码
     */
    public static String decode(String src, String encoding) {
        return decode(src, Charset.forName(encoding));
    }
    
    /**
     * 指定字符集进行Base64解码
     */
    public static String decode(String src, Charset charset) {
        return new String(decode(src), charset);
    }
    
    /*************************************************************************************************/
    // 文件名编解码
    /*************************************************************************************************/
    
    /**
     * 使用UTF-8进行Base64编码，并替换+为~，/替换成_
     */
    public static String encodeFileUrlUTF8(String src) {
        return encode(src, _UTF_8_C_).replaceAll("\\+", "~").replaceAll("/", "_");
    }
    
    /**
     * 使用UTF-8进行Base64解码，并替换+为~，/替换成_
     */
    public static String decodeFileUrlUTF8(String src) {
        return decode(src.replaceAll("~", "\\+").replaceAll("_", "/"), _UTF_8_C_);
    }
    
    /*************************************************************************************************/
    // URL编解码
    /*************************************************************************************************/
    
    /**
     * 使用UTF-8进行Base64编码，并替换/为_
     */
    public static String encodeFileNameUTF8(String src) {
        return encode(src, _UTF_8_C_).replaceAll("/", "_");
    }
    
    /**
     * 使用UTF-8进行Base64解码，并替换/为_
     */
    public static String decodeFileNameUTF8(String src) {
        return decode(src.replaceAll("_", "/"), _UTF_8_C_);
    }
    
    
}
